package cn.nkpro.elcube.security.validate2;

import cn.nkpro.elcube.basic.Constants;
import cn.nkpro.elcube.basic.NkProperties;
import cn.nkpro.elcube.data.redis.RedisSupport;
import cn.nkpro.elcube.security.bo.UserDetails;
import cn.nkpro.elcube.utils.DateTimeUtilz;
import cn.nkpro.elcube.utils.UUIDHexGenerator;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedCredentialsNotFoundException;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;

import javax.annotation.PostConstruct;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.*;
import java.util.stream.Collectors;

@Component
public class AuthenticationUtils{


    @Autowired
    protected RedisSupport<Object> redisSupport;
    @Autowired
    private NkProperties nkProperties;

    private static long validityTime;

    @PostConstruct
    public void getValidityTime(){validityTime = nkProperties.getValidityTime();}

    String obtainParam(ServletRequest request, String param) {
        String value = ((HttpServletRequest)request).getHeader(param);
        if (Objects.isNull(value)) {
            value = request.getParameter(param);
        }
        return StringUtils.defaultString(value);
    }

    // 1、校验时间戳
    public static void validateTimestamp(String timestamp){
        try{
            long time = Long.parseLong(timestamp);
            long now  = DateTimeUtilz.nowSeconds();
            if(time < now - validityTime || time > now + validityTime){
                throw new BadCredentialsException("无效的timestamp");
            }
        }catch (NumberFormatException e){
            throw new BadCredentialsException("无效的timestamp");
        }
    }

    public static void simpleValidate(UserDetails details){
        if(details==null){
            throw new UsernameNotFoundException("账号没有找到");
        }
        if(details.getLocked()!=null && details.getLocked()==1){
            throw new BadCredentialsException("账号已禁用");
        }
        if(StringUtils.isAnyBlank(details.getValidFrom(),details.getValidTo())){
            throw new BadCredentialsException("账号已过期");
        }
        String today = DateTimeUtilz.todayShortString();
        if(details.getValidFrom().compareTo(today)>0){
            throw new BadCredentialsException(String.format("账号有效期为:%s-%s",details.getValidFrom(),details.getValidTo()));
        }
        if(details.getValidTo().compareTo(today)<0){
            throw new BadCredentialsException(String.format("账号有效期为:%s-%s",details.getValidFrom(),details.getValidTo()));
        }
    }

    void clearErrorsLogin(String username){
        redisSupport.delete(Constants.CACHE_AUTH_ERROR+username);
    }

    void throwsErrorsLogin(String username){
        String key = Constants.CACHE_AUTH_ERROR+username;
        long increment = redisSupport.increment(key, 1);
        if(increment>=5){
            redisSupport.expire(key, 60 * 60);
            throw new BadCredentialsException("密码错误次数过多，账号已被锁定，请1小时后再试");
        }else{
            redisSupport.expire(key,60 * 5 * increment);
            throw new BadCredentialsException("密码错误");
        }
    }

    void checkNonce(String key,String nonce){
        String cache = String.format("%s%s%s", Constants.CACHE_AUTH_NONCE, key, nonce);
        long increment = redisSupport.increment(cache, 1);
        if(increment>1){
            throw new BadCredentialsException("签名已失效");
        }
        redisSupport.expire(cache,validityTime);
    }
    /**
     *
     * @param username 用户名
     * @param verKey   验证码key 如果null，则不校验验证码
     * @param verCode  验证码code
     */
    void validateErrorsLogin(String username, boolean validateVer, String verKey, String verCode){
        // 2、校验错误登陆次数
        String key = Constants.CACHE_AUTH_ERROR+username;
        Integer errorTimes = (Integer) redisSupport.get(key);

        // 如果用户仍然继续尝试登陆，那么延长锁定时间 重试6次锁定4小时，7次9小时，8次16小时，9次25小时，10次36小时，以此类推
        if(errorTimes!=null && errorTimes>5){
            long increment = redisSupport.increment(key, 1);
            long hour = (long) Math.pow(increment-4,2);
            redisSupport.expire(key, 60 * 60 * hour);
            throw new BadCredentialsException("账号已被锁定，请"+hour+"小时后再试");
        }
        if(validateVer){
            if(errorTimes!=null){
                // 校验验证码
                Object code = redisSupport.get(Constants.CACHE_TEMP_VER_CODE + verKey);
                if(code==null)
                    throw new BadCredentialsException("验证码已过期，请重新获取");
                if(!Objects.equals(code, verCode)){
                    throw new BadCredentialsException("验证码不正确");
                }
            }
            // 如果用户获取了验证码，也要校验一次
            if(StringUtils.isNotBlank(verKey)){
                Object code = redisSupport.get(Constants.CACHE_TEMP_VER_CODE + verKey);
                if(code!=null && !Objects.equals(code, verCode)){
                    throw new BadCredentialsException("验证码不正确");
                }
            }
        }
    }

    public NkToken grantToken(String client, String username) {

        NkToken token = new NkToken();
        token.setUsername(username);
        token.setToken(UUIDHexGenerator.nonce().toLowerCase());
        token.setExpire(60*30*1000);

        String cache = String.format("%s%s:%s",Constants.CACHE_AUTH_TOKEN,username,client);
        redisSupport.set(cache, token);
        redisSupport.expire(cache, 60 * 30);// 有效期30分钟
        return token;
    }

    public NkToken getToken(String client, String username){
        return (NkToken) redisSupport.get(String.format("%s%s:%s", Constants.CACHE_AUTH_TOKEN,username, client));
    }

    public void extendToken(String client, String username){
        redisSupport.expire(String.format("%s%s:%s",Constants.CACHE_AUTH_TOKEN,username,client), 60 * 30);// 有效期30分钟
    }

    public static String signature(String url, Map<String,?> parameterMap, String timestamp, String secret,String nonce){
        List<String> parameterStr = new ArrayList<>();
        parameterStr.add(String.format("timestamp=%s",timestamp));
        parameterStr.add(String.format("secret=%s",secret));
        parameterStr.add(String.format("nonce=%s",nonce));
        parameterMap
                .forEach((key, value) ->{
                    if(value!=null){
                        if(value.getClass().isArray()){
                            Arrays.stream((Object[]) value)
                                    .forEach(v -> parameterStr.add(String.format("%s=%s", key, v)));
                        }else{
                            parameterStr.add(String.format("%s=%s", key, value));
                        }
                    }
                });
        return DigestUtils.sha1Hex(url+'?'+parameterStr.stream().sorted().collect(Collectors.joining("&")));
    }

    public static boolean validateSignature(String url, Map<String,?> parameterMap, String timestamp, String secret,String nonce, String targetSignature){
        boolean valid = StringUtils.equalsIgnoreCase(
                signature(url,parameterMap,timestamp,secret,nonce),
                targetSignature
        );
        if(!valid){
            if(url.startsWith("/api")){
                valid = StringUtils.equalsIgnoreCase(
                        signature(url.substring(4),parameterMap,timestamp,secret,nonce),
                        targetSignature
                );
            }
        }
        return valid;
    }
    public void commence(HttpServletResponse response, AuthenticationException e) throws IOException {

        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);

        if(e instanceof PreAuthenticatedCredentialsNotFoundException){
            // 账号未绑定
            response.setStatus(901);
        }

        response.setCharacterEncoding("utf-8");
        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        PrintWriter printWriter = response.getWriter();
        printWriter.print(e.getMessage());
        printWriter.flush();
        printWriter.close();
    }

}
